package es.guiguegon.sineview;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.AttrHelper;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.Path;
import ohos.agp.utils.Color;
import ohos.agp.window.service.Display;
import ohos.agp.window.service.DisplayAttributes;
import ohos.agp.window.service.DisplayManager;
import ohos.app.Context;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.utils.geo.Rect;

import java.util.Optional;


/**
 * @ProjectName: sinview
 * @ClassName: SineView
 * @Description:
 * @Author: lr
 * @CreateDate: 2021/5/27 11:35
 */
public class SineView extends Component implements Component.DrawTask{
    private static HiLogLabel logLabel = new HiLogLabel(HiLog.LOG_APP, 0x001, "SineView");
    protected static final float STEP_X = 1f;
    //Default vars
    protected static final float DEFAULT_AMPLITUDE = 100f;
    protected static final float DEFAULT_ALPHA = 1f;
    // We use this offset to avoid a black line at the bottom of the view
    protected static final float DEFAULT_PHASE = 1f;
    protected static final float DEFAULT_FREQUENCY = 1f;
    protected static final int DEFAULT_ANIMATION_TIME_IN_MILLIS = 2000;
    protected static final float DEFAULT_PERIODS = 1f;
    protected static final boolean DEFAULT_FILL = true;
    protected static final Color DEFAULT_COLOR = Color.BLACK;
    private final static int Y_OFFSET = 0;
    protected Path mPath;
    protected Paint mPaint = new Paint();
    protected Rect mRectF = new Rect(0, 0, 100, 100);

    protected float paramSineAlpha = DEFAULT_ALPHA;
    protected float paramSinePhase = DEFAULT_PHASE;
    protected float paramSineAmplitude = DEFAULT_AMPLITUDE;
    protected int paramSineAnimTime = DEFAULT_ANIMATION_TIME_IN_MILLIS;
    protected float paramSinePeriod = DEFAULT_PERIODS;
    protected boolean paramSineFill = DEFAULT_FILL;
    protected Color paramSineColor = DEFAULT_COLOR;

    protected float amplitude = DEFAULT_AMPLITUDE;
    protected float frequency = DEFAULT_FREQUENCY;
    protected float phase = DEFAULT_PHASE;
    protected float scaleY;

    protected float measuredHeight;
    protected float measuredWidth;

    protected float step = 0f;
    protected SinValueAnimator progressValueAnimator;
    private boolean isAnimating;

    private long currentPlayTime;

    public SineView(Context context) {
        super(context);
        initialize();

    }

    public SineView(Context context, AttrSet attrSet) {
        super(context, attrSet);

        getCustomAttributes(attrSet);
        initialize();

    }

    public SineView(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        getCustomAttributes(attrSet);
        initialize();

    }

    public SineView(Context context, AttrSet attrSet, int resId) {
        super(context, attrSet, resId);
        getCustomAttributes(attrSet);
        initialize();
    }

    @Override
    public void setLayoutRefreshedListener(LayoutRefreshedListener listener) {
        super.setLayoutRefreshedListener(listener);
    }

    protected void initialize() {
        mPaint.setAntiAlias(true);
        mPaint.setColor(paramSineColor);
        if (paramSineFill) {
            mPaint.setStyle(Paint.Style.FILLANDSTROKE_STYLE);
        } else {
            mPaint.setStyle(Paint.Style.STROKE_STYLE);
        }
        mPaint.setAlpha((int) (1.0f * paramSineAlpha));
        // setLayerType(LAYER_TYPE_HARDWARE, mPaint);
        prepareWave();
    }

    protected void getCustomAttributes(AttrSet attrSet) {
        paramSineAlpha = AttrValue.get(attrSet, "sv_sine_alpha", DEFAULT_ALPHA);
        paramSinePhase = AttrValue.get(attrSet, "sv_sine_phase", DEFAULT_PHASE);
        paramSineAmplitude = AttrValue.get(attrSet, "sv_sine_amplitude", DEFAULT_AMPLITUDE);
        paramSineAnimTime =
                AttrValue.get(attrSet, "sv_sine_animation_time_millis", DEFAULT_ANIMATION_TIME_IN_MILLIS);
        paramSinePeriod = AttrValue.get(attrSet, "sv_sine_periods_to_show", DEFAULT_PERIODS);
        paramSineColor = AttrValue.get(attrSet, "sv_sine_color", DEFAULT_COLOR);
        paramSineFill = AttrValue.get(attrSet, "sv_sine_fill", DEFAULT_FILL);
    }

    public void prepareWave() {
        measuredWidth = getDisplay().width;  
        measuredHeight = getDisplay().height;
        mRectF.set(0, 0, getDisplay().width, getDisplay().height);

        setValueAnimator();
        calculateFrequency();
        calculateAmplitude();
        calculateScaleY();
        calculatePhase();
        invalidate();
        progressValueAnimator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float v) {
                step = v;
                calculatePhase();
                invalidate();
            }
        });

        progressValueAnimator.setStateChangedListener(new Animator.StateChangedListener() {
            @Override
            public void onStart(Animator animator) {

            }

            @Override
            public void onStop(Animator animator) {

            }

            @Override
            public void onCancel(Animator animator) {

            }

            @Override
            public void onEnd(Animator animator) {
                // setLayerType(View.LAYER_TYPE_NONE, mPaint);
            }

            @Override
            public void onPause(Animator animator) {

            }

            @Override
            public void onResume(Animator animator) {

            }
        });
    }

    protected float generateSinePoint(float x) {
        Float f = new Float((amplitude * Math.sin(x * frequency - phase)));
        return f.floatValue();
    }

    protected void calculateAmplitude() {
        amplitude = AttrHelper.vp2px(paramSineAmplitude,mContext) ;
    }

    protected void calculateFrequency() {
        frequency = (float) (2 * Math.PI * paramSinePeriod / measuredWidth);
    }

    protected void calculateScaleY() {
        scaleY = amplitude;
    }

    protected void calculatePhase() {
        phase = (float) (paramSinePhase * (Math.PI / 2) + step);
    }

    public boolean isAnimating() {
        return isAnimating;
    }

    protected void setValueAnimator() {
        progressValueAnimator = SinValueAnimator.ofFloat(0, (float) (2 * Math.PI));
        progressValueAnimator.setDuration(paramSineAnimTime);
        progressValueAnimator.setCurveType(Animator.CurveType.LINEAR);
        progressValueAnimator.setLoopedCount(-1);
    }

    public void startWave() {
        progressValueAnimator.start();
        isAnimating = true;
    }

    public void pauseWave() {
        if (isAnimating) {
            currentPlayTime = progressValueAnimator.getDuration();
            progressValueAnimator.cancel();
            isAnimating = false;
        }
    }

    public void resumeWave() {
        progressValueAnimator.start();
        progressValueAnimator.setDuration(currentPlayTime);
        isAnimating = true;
    }

    public void stopWave() {
        try {
            if (progressValueAnimator != null) {
                progressValueAnimator.end();
                isAnimating = false;
            }
        } catch (Exception e) {
            HiLog.error(logLabel, "[stopWave]", e);
        }
    }

    protected void generatePath(Canvas canvas) {
        mPath = new Path();
        float x = 0;
        float y = generateSinePoint(x);
        mPath.moveTo(x, y);
        for (; x < mRectF.getWidth(); x += STEP_X) {
            y = generateSinePoint(x);
            mPath.lineTo(x + mRectF.left, y + mRectF.top + scaleY);
        }
        mPath.lineTo(measuredWidth, measuredHeight + Y_OFFSET);
        mPath.lineTo(0, measuredHeight + Y_OFFSET);
        mPath.close();
        canvas.drawPath(mPath, mPaint);
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        generatePath(canvas);
    }

    @Override
    public void invalidate() {
        super.invalidate();
       addDrawTask(this::onDraw);
    }

    private DisplayAttributes getDisplay() {
        DisplayAttributes displayAttributes = null;
        Optional<Display> optionalDisplay = DisplayManager.getInstance().getDefaultDisplay(mContext);
        if (optionalDisplay.isPresent()) {
            displayAttributes = optionalDisplay.get().getAttributes();
        }
        return displayAttributes;
    }
}
